Skip to content

fix: attribute instrument bootstrap() warnings to the user's call site (#202) - #208

Merged
lesnik512 merged 1 commit into
mainfrom
fix/202-bootstrap-warning-attribution
Sep 12, 2026
Merged

fix: attribute instrument bootstrap() warnings to the user's call site (#202)#208
lesnik512 merged 1 commit into
mainfrom
fix/202-bootstrap-warning-attribution

Conversation

@lesnik512

Copy link
Copy Markdown
Member

Closes #202.

The defect, re-measured

The issue's line numbers predate c210381 (#199), which moved both call sites. Re-measured on
current main, the two shapes still diverge exactly as described:

warning attributed to
swagger_path differs from docs_url bootstrappers/base.py:137one_instrument.bootstrap()
OTLP exporter not installed (both transports) bootstrappers/fastapi_bootstrapper.py:118super().bootstrap()

Both are lite-bootstrap's own source, and they differ by one frame because
FastAPISwaggerInstrument.bootstrap() is called straight from the loop while
FastAPIOpenTelemetryInstrument.bootstrap() calls super().bootstrap() first. No single literal
stacklevel is right for both, and any instrument that grows or loses a super() call shifts its
own attribution with nothing to notice.

The decision the issue left open

The decision to make first is which of those two the user can act on. Both are lines in their
code, so both are defensible.

Resolved as the bootstrap() call site. Both warnings are about the config the user passed — an
optional dependency that is missing, a swagger_path that is being ignored — so the line they
would edit in response is where they called bootstrap(), not a frame inside an instrument. That
also makes the two shapes converge rather than stay one frame apart, which is the part of the
defect a corrected literal would have left standing.

So warn_at_caller it is, as the issue anticipated, with one addition it did not: the helper
hardcoded UserWarning, and the two OpenTelemetry sites raise InstrumentDependencyMissingWarning.
A category parameter defaulting to UserWarning covers both, leaving the three existing
config-path callers untouched.

Renaming _CONSTRUCTION_MODULES to _INTERNAL_MODULES follows from the widened contract: the
frames being skipped are no longer only construction frames. It is module-private, so no alias is
owed.

Tests

Two invariant tests, one per depth, both through FastAPIBootstrapper.bootstrap(). Either alone
would pass against a literal stacklevel — it is the pair that pins the property. Both were
written first and both failed on the two paths in the table above.

The existing test_missing_exporter_warning_points_at_the_caller_of_bootstrap covers the
instrument called on its own, where the walk already stopped at the right frame; its "what breaks
it" paragraph now names the helper rather than the literal it used to describe.

The filter-and-compare assertion those three tests share is now conftest.warning_source_files().
It compares whole lists rather than indexing caught[0]: a warning raised from the wrong frame and
one raised twice are different bugs, and the list form catches both.

Out of scope, per the issue

BaseBootstrapper's two construction-time warnings keep their literal stacklevel, as #202
directed — they land on the user's line today. helpers/fastapi_helpers.py is likewise untouched:
it warns while serving a request, with no user frame anywhere on the stack.

AGENTS.md's rule needed rewriting, as the issue required. Its old sentence — warnings outside
config construction keep their literal stacklevel, targeting the bootstrapper's caller — is now
wrong on both halves. The replacement also inventories the three literal sites that survive and why
each is deliberate, including the detail that _attach_teardown_once's stacklevel=3 is one
shallower than _select_instruments' stacklevel=4 because the subclass __init__ calls it
directly.

Verification

ruff format --check, ruff check --no-fix and ty check clean. 277 tests pass at 100% statement
coverage.

#202)

The three warnings raised while an instrument bootstraps carried a literal
`stacklevel`, which encodes a frame depth that is not constant. Measured on
`main` before the change:

    opentelemetry_instrument.py  ->  bootstrappers/fastapi_bootstrapper.py:118
    fastapi_bootstrapper.py      ->  bootstrappers/base.py:137

Both are lite-bootstrap's own source. They differ by one frame because
`FastAPISwaggerInstrument.bootstrap()` is called straight from the loop in
`BaseBootstrapper.bootstrap()` while `FastAPIOpenTelemetryInstrument.bootstrap()`
calls `super().bootstrap()` first, so no single literal can be right for both,
and any instrument that grows or loses a `super()` call shifts its own
attribution silently.

Route all three through `warn_at_caller`, which walks out to the first frame
outside `lite_bootstrap`. On this path that frame is the user's `bootstrap()`
call, which is the line they can act on: the warnings say a dependency is
missing or that `swagger_path` is being ignored, and the fix for both is in the
config they passed, not inside the instrument. The helper needed one addition,
a `category` parameter, since the OpenTelemetry warnings are
`InstrumentDependencyMissingWarning` rather than plain `UserWarning`.

Two invariant tests pin both depths through the bootstrapper, one per shape;
either alone would pass against a literal. The existing OpenTelemetry test
covers the instrument called on its own, where the walk already stopped at the
right frame, and its "what breaks it" paragraph is updated to name the helper.

Out of scope, per the issue: the two warnings reached from
`BaseBootstrapper.__init__` are correct today and keep their literal
`stacklevel`, and `helpers/fastapi_helpers.py` warns from inside a request
handler where no user frame exists at all.

Closes #202
@lesnik512
lesnik512 merged commit f97b4c9 into main Sep 12, 2026
12 checks passed
@lesnik512
lesnik512 deleted the fix/202-bootstrap-warning-attribution branch September 12, 2026 11:08
@lesnik512 lesnik512 mentioned this pull request Sep 12, 2026
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Instrument bootstrap() warnings are attributed to lite-bootstrap, not the user

1 participant